﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace ZeroBugBounce.Missive
{
	public partial class Messenger
	{
		Type GetClosedTypeThatImplementsHandlerT(Handler handler)
		{
			return handler.GetType().ListAllBaseTypes()
				.Where(t =>
					t.IsGenericType &&
					t.GetGenericTypeDefinition() == typeof(Handler<>))
				.FirstOrDefault();
		}

		//public void AddHandlerType(Type handlerType)
		//{
		//    AddHandlerType(Topic.Default, handlerType);
		//}

		//public void AddHandlerType(Topic topic, Type handlerType)
		//{
		//    if (!handlerType.IsGenericType)
		//    {
		//        throw new ArgumentException("handlerType must be an open generic type");
		//    }

		//    if (!handlerType.IsGenericTypeDefinition)
		//    {
		//        throw new ArgumentException("handlerType must be an open generic type");
		//    }

		//    var openMessageType = handlerType
		//                            .GetMethod("Handle").GetParameters().First().ParameterType
		//                            .GetGenericArguments().First();

		//    if (!openMessageType.ContainsGenericParameters)
		//    {
		//        throw new ArgumentException("handlerType must have a Handle method that contains generic parameters");
		//    }

		//    var openMessageTypeDefinition = openMessageType.GetGenericTypeDefinition();

		//    HandlerLock.Enter();
		//    HandlerTypes.Add(new HandlerType
		//    {
		//        OpenHandlerType = handlerType,
		//        OpenMessageType = openMessageTypeDefinition,
		//        Topic = topic
		//    });

		//    // now fix up existing handlers by activating handlers for any valid
		//    // known types:

		//    foreach (var typeHandle in ActivatedMessageTypeOfHandlerTypesGuard)
		//    {
		//        Type messageType = Type.GetTypeFromHandle(typeHandle);

		//        if (messageType.IsGenericType)
		//        {
		//            var messageTypeDefinition = messageType.GetGenericTypeDefinition();
		//            if (openMessageTypeDefinition == messageTypeDefinition) // a match!
		//            {
		//                Type[] messageTypeParameters = messageType.GetGenericArguments().ToArray();
		//                var closedHandlerType = handlerType.MakeGenericType(messageTypeParameters);
		//                this.AddHandler(topic, (Handler)Activator.CreateInstance(closedHandlerType));
		//            }
		//        }
		//    }

		//    HandlerLock.Leave();
		//}

		//public void AddHandlerFast<T>(Handler<T> handler)
		//{
		//    HandlerLock.Enter();
		//    var key = new Tuple<Topic, Type>(handler.Topic, typeof(T));
		//    // add this handler to any existing topics that support it:			
			

		//    // if the key for this exact topic/type doesn't exist, add it with this handler:
		//    if (!CachedHandlers.ContainsKey(key))
		//    {
		//        CachedHandlers.Add(key, new Handler<T>[] { handler });
		//    }	

		//    Handlers.Add(handler);
		//    HandlerLock.Leave();
		//}

		void RebuildHandlerCache()
		{
			var knownTopicTypeTuples = CachedHandlers.Keys.ToArray();
			Dictionary<Tuple<Topic, Type>, Handler[]> newCache = new Dictionary<Tuple<Topic, Type>, Handler[]>();

			foreach (var tuple in knownTopicTypeTuples)
			{
				var handlers = Handlers.Where(h =>
					h.HandledType == tuple.Item2 &&
					tuple.Item1.IsUnder(h.Topic)).ToArray();

				var arrayType = typeof(Handler<>).MakeGenericType(tuple.Item2);
				var handlerTypedArray = Array.CreateInstance(arrayType, handlers.Length);

				handlers.CopyTo(handlerTypedArray, 0);

				newCache[tuple] = (Handler[])handlerTypedArray;
			}

			CachedHandlers = newCache;
		}

		public void AddHandlers(params Handler[] handlers)
		{
			HandlerLock.Enter();
			foreach(var handler in handlers)
			{
				Handlers.Add(handler);
				var key = new Tuple<Topic, Type>(handler.Topic ?? Topic.Default, handler.HandledType);

				if (!CachedHandlers.Keys.Contains(key))
					CachedHandlers.Add(key, null);
			}

			RebuildHandlerCache();
			HandlerLock.Leave();
		}

		public void AddHandler<T>(Handler<T> handler)
		{
			HandlerLock.Enter();
			Handlers.Add(handler);
			var key = new Tuple<Topic, Type>(handler.Topic, typeof(T));

			if (!CachedHandlers.Keys.Contains(key))
			{
				CachedHandlers.Add(key, null);
			}

			RebuildHandlerCache();
			HandlerLock.Leave();

			//HandlerLock.Enter();
			//var key = new Tuple<Topic, Type>(handler.Topic, typeof(T));
			//if (!CachedHandlers.ContainsKey(key))
			//{
			//    CachedHandlers[key] = null;
			//}

			//foreach (var cacheKey in CachedHandlers.Keys.ToArray())
			//{
			//    if (cacheKey.Item2 == typeof(T) && handler.Topic.IsUnder(cacheKey.Item1))
			//    {
			//        Handler<T>[] currentCachedHandlers = CachedHandlers[key] as Handler<T>[];

			//        if (currentCachedHandlers == null)
			//        {
			//            currentCachedHandlers = new Handler<T>[1];
			//            currentCachedHandlers[0] = handler;
			//            CachedHandlers[key] = currentCachedHandlers;
			//        }
			//        else
			//        {
			//            Handler<T>[] newCachedHandlers = new Handler<T>[currentCachedHandlers.Length + 1];
			//            currentCachedHandlers.CopyTo(newCachedHandlers, 0);
			//            newCachedHandlers[currentCachedHandlers.Length] = handler;
			//            CachedHandlers[key] = newCachedHandlers;
			//        }
			//    }
			//}

			//Handlers.Add(handler);
			//HandlerLock.Leave();
		}

		// this method will be slower than the generic one in adding an handler
		// but the added handler will be as fast
		public void AddHandler(Topic topic, Handler handler)
		{
			HandlerLock.Enter();
			Handlers.Add(handler);

			var key = new Tuple<Topic, Type>(handler.Topic, handler.HandledType);
			if (!CachedHandlers.Keys.Contains(key))
			{
				CachedHandlers.Add(key, null);
			}

			var knownTopicTypeTuples = CachedHandlers.Keys.ToArray();
			Dictionary<Tuple<Topic, Type>, Handler[]> newCache = new Dictionary<Tuple<Topic, Type>, Handler[]>();

			foreach (var tuple in knownTopicTypeTuples)
			{
				var handlers = Handlers.Where(h =>
					h.HandledType == tuple.Item2 &&
					tuple.Item1.IsUnder(h.Topic)).ToArray();

				var arrayType = typeof(Handler<>).MakeGenericType(tuple.Item2);
				var handlerTypedArray = Array.CreateInstance(arrayType, handlers.Length);

				handlers.CopyTo(handlerTypedArray, 0);

				newCache[tuple] = (Handler[])handlerTypedArray;
			}

			CachedHandlers = newCache;
			HandlerLock.Leave();
		}

		public void AddHandler(Handler handler)
		{
			AddHandler(DefaultTopic, handler);
		}

		// There are a lot of methods around handling sent message
		// this is mostly convenience as they all aim at the same goal:
		// accept an incoming message and do some processing

		public IDisposable Handle<T, TReply>(object topic, Action<ReplyHandlerContext<T, TReply>> method, DispatchMode dispatchMode)
		{
			return Handle<T, TReply>(topic, method, dispatchMode, true);
		}

		public IDisposable Handle<T, TReply>(object topic, Action<ReplyHandlerContext<T, TReply>> method, DispatchMode dispatchMode, Boolean cancelable)
		{
			Action<HandlerContext<T>> contextMethod = (HandlerContext<T> context) =>
			{
				var replyContext = context as ReplyHandlerContext<T, TReply>;
				if (replyContext != null)
				{
					method(replyContext);
				}
				else
				{
					// pass a ReplyHandlerContext that does nothing:
					var noOp = new ReplyHandlerContext<T, TReply>();
					noOp.ReplyCallback = delegate { };
					noOp.Message = context.Message;

					method(noOp);
				}
			};

			return Handle<T>(topic, contextMethod, dispatchMode, cancelable);
		}

		public IDisposable Handle<T>(object topic, Action<HandlerContext<T>> action, DispatchMode dispatchMode)
		{
			return Handle<T>(topic, action, dispatchMode, true);
		}

		public IDisposable Handle<T>(object topic, Action<HandlerContext<T>> action, DispatchMode dispatchMode, Boolean cancelable)
		{
			Topic handlerTopic = ParseTopic(topic);

			var handler = new LambdaHandler<T>
			(
				handlerTopic,
				dispatchMode,
				cancelable,
				action
			);

			AddHandler<T>(handler);

			return new Disposable(() =>
			{
				HandlerLock.Enter();
				Handlers.Remove(handler);
				foreach (var kvp in CachedHandlers.ToArray())
				{
					Handler<T>[] newCachedHandlers = kvp.Value.Where(h => h != handler).OfType<Handler<T>>().ToArray();
					if (newCachedHandlers.Length < kvp.Value.Length)
					{
						CachedHandlers[kvp.Key] = newCachedHandlers;
					}
				}
				HandlerLock.Leave();
			});
		}

		public IDisposable Handle<T, TReply>(object topic, Func<T, TReply> method, DispatchMode dispatchMode)
		{
			Action<HandlerContext<T>> contextMethod = (HandlerContext<T> context) =>
			{
				var replyContext = context as ReplyHandlerContext<T, TReply>;
				if (replyContext != null)
				{
					var reply = method(replyContext.Message);
					replyContext.ReplyCallback(reply);
				}
				else
				{
					method(context.Message);
				}
			};

			return Handle<T>(topic, contextMethod, dispatchMode);
		}

		public IDisposable Handle<T, TReply>(object topic, Func<T, CancellationToken, TReply> method, DispatchMode dispatchMode)
		{
			Action<HandlerContext<T>> contextMethod = (HandlerContext<T> context) =>
			{
				var replyContext = context as ReplyHandlerContext<T, TReply>;
				if (replyContext != null)
				{
					var reply = method(replyContext.Message, context.CancellationToken);
					replyContext.ReplyCallback(reply);
				}
				else
				{
					method(context.Message, context.CancellationToken);
				}
			};

			return Handle<T>(topic, contextMethod, dispatchMode, true);
		}

		public IDisposable Handle<T>(object topic, Action<T> method)
		{
			return Handle(topic, method, DispatchMode.Asynchronous);
		}
		
		public IDisposable Handle<T>(object topic, Action<T, CancellationToken> method)
		{
			return Handle<T>(topic, method, DispatchMode.Asynchronous);
		}

		public IDisposable Handle<T>(Action<T> method)
		{
			return Handle(Topic.DefaultTopicText, method, DispatchMode.Asynchronous);
		}

		public IDisposable Handle<T>(Action<T, CancellationToken> method)
		{
			return Handle(Topic.DefaultTopicText, method, DispatchMode.Asynchronous);
		}

		public IDisposable Handle<T>(Action<T> method, DispatchMode dispatchMode)
		{
			return Handle(Topic.DefaultTopicText, method, dispatchMode);
		}

		public IDisposable Handle<T>(Action<T, CancellationToken> method, DispatchMode dispatchMode)
		{
			return Handle(Topic.DefaultTopicText, method, dispatchMode);
		}

		public IDisposable Handle<T>(object topic, Action<T> method, DispatchMode dispatchMode)
		{
			Action<HandlerContext<T>> contextMethod = (HandlerContext<T> context) => method(context.Message);
			return Handle<T>(topic, contextMethod, dispatchMode);
		}

		public IDisposable Handle<T>(object topic, Action<T, CancellationToken> method, DispatchMode dispatchMode)
		{
			Action<HandlerContext<T>> contextMethod = (HandlerContext<T> context) => method(context.Message, context.CancellationToken);
			return Handle<T>(topic, contextMethod, dispatchMode, true);
		}

		public IDisposable Handle<T, TReply>(object topic, Func<T, TReply> method)
		{
			return Handle<T, TReply>(topic, method, DispatchMode.Asynchronous);
		}

		public IDisposable Handle<T, TReply>(object topic, Func<T, CancellationToken, TReply> method)
		{
			return Handle<T, TReply>(topic, method, DispatchMode.Asynchronous);
		}

		public IDisposable Handle<T, TReply>(Func<T, TReply> method, DispatchMode dispatchMode)
		{
			return Handle(Topic.DefaultTopicText, method, dispatchMode);
		}

		public IDisposable Handle<T, TReply>(Func<T, CancellationToken, TReply> method, DispatchMode dispatchMode)
		{
			return Handle(Topic.DefaultTopicText, method, dispatchMode);
		}

		public IDisposable Handle<T, TReply>(Func<T, TReply> method)
		{
			return Handle(method, DispatchMode.Asynchronous);
		}

		public IDisposable Handle<T, TReply>(Func<T, CancellationToken, TReply> method)
		{
			return Handle(method, DispatchMode.Asynchronous);
		}

	}
}
